/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using Scorer = System.Qizx.Api.FullText.Scorer;
namespace System.Qizx.Queries.Iterators
{
	
	/// <summary> Represents the 'all' clauses of FT. 
	/// Sub-iterators can be any other FT iterator like Term, Or, Phrase.
	/// <p>
	/// DIFFERENT than IntersectionIterator, because enumerates the Cartesian product
	/// of sub-iterators. 
	/// <p>
	/// <b>Always</b> used as a predicate iterator within a ContainerIterator.
	/// </summary>
	public class AllIterator:PolyIterator
	{
		override protected internal PostingIterator[] ChildrenInOrder
		{
			get
			{
				if (ordered)
					return iterators;
				return base.ChildrenInOrder;
			}
			
		}
		protected internal PostingIterator fastest;
		// If ordered and using constraints like "window < N" or "distance == 0",
		// (typically: PhraseIter) then it is possible to iterate forward only
		protected internal bool fastPhrase;
		protected internal bool fastInit;
		
		public AllIterator(PostingIterator[] iterators):base(iterators)
		{
		}
		
		public override PostingIterator bornAgain()
		{
			return copyConstraints(new AllIterator(copy(iterators)));
		}
		
		private void  init()
		{
			if (!fastInit)
			{
				fastInit = true;
				// useful only for this:
				fastPhrase = ordered && constraints != null && (constraints.window > 0 || constraints.maxDistance == 0);
				
				// iterate fastest on the last subiter: (except if 'fastPhrase')
				fastest = fastPhrase?iterators[0]:iterators[iterators.Length - 1];
			}
		}
		
		protected internal override bool basicSkipToNode(int nodeId, int limit)
		{
			return inRange(nodeId, limit);
		}
		
		public override void  resetToNode(int nodeId)
		{
			init();
			base.resetToNode(nodeId);
			// remember range start
			currentResetPos = nodeId;
		}
		
		public override bool inRange(int rangeStart, int rangeEnd)
		{
			init();
			currentResetPos = rangeStart;
			if (iterators.Length == 0)
				return false;
			
			// Dispatch to specialized methods:
			if (fastPhrase)
				return phraseInRange(rangeStart, rangeEnd);
			
			// quick unordered test:
			int start = rangeStart;
			for (int i = 0, size = iterators.Length; i < size; i++)
			{
				if (!iterators[i].inRange(start, rangeEnd))
					return false;
				if (ordered)
					start = iterators[i].NodeId;
			}
			computeBounds();
			if ((constraints == null || checkFTConstraints(rangeStart, rangeEnd)) && checkFilters())
				return true;
			// failure of constraints: use heavier means
			return nextBefore(rangeEnd);
		}
		
		// Typically used for a phrase 
		//    (more generally: ordered + max window constraint).
		//
		private bool phraseInRange(int rangeStart, int rangeEnd)
		{
			int start = rangeStart;
			
			for (; ; )
			{
				for (int i = 0, size = iterators.Length; i < size; i++)
				{
					if (!iterators[i].skipToNode(start, rangeEnd))
						return false;
					start = iterators[i].NodeId + 1;
					if (start > rangeEnd)
					{
						// strict > because of + 1
						return false;
					}
				}
				computeBounds();
				if ((constraints == null || checkFTConstraints(rangeStart, rangeEnd)) && checkFilters())
					return true;
				
				// move first iterator
				start = iterators[0].NodeId + 1;
			}
		}
		
		// assume that sub-iters are already on a hit
		public override bool nextBefore(int rangeEnd)
		{
			init();
			if (fastPhrase)
				return phraseNextBefore(rangeEnd);
			
			for (; ; )
			{
				if (!fastest.nextBefore(rangeEnd))
				{
					// end of fastest subiter: try move previous sub-iter and so on
					if (!moveOtherIters(iterators.Length - 2, rangeEnd))
						return false;
					// OK retry on fastest
					if (!fastest.nextBefore(rangeEnd))
						return false; // can happen if ordered
				}
				// OK, compute first and last position of this match:
				computeBounds();
				// window/distance constraints
				if (constraints != null && !checkFTConstraints(currentResetPos, rangeEnd) || !checkFilters())
					continue;
				return true;
			}
		}
		
		/// <summary> Recursively moves the previous iterators.</summary>
		private bool moveOtherIters(int rank, int rangeEnd)
		{
			if (rank < 0)
				return false;
			if (!iterators[rank].nextBefore(rangeEnd))
			{
				if (!moveOtherIters(rank - 1, rangeEnd))
					return false;
				// iterator at this level has been reset:
				if (!iterators[rank].goNextNode(rangeEnd))
				{
					return false;
				}
			}
			// reset iterator at next level:
			if (ordered)
			{
				// start from *current* token not from next token
				iterators[rank + 1].resetToNode(iterators[rank].NodeId);
			}
			else
				iterators[rank + 1].resetToNode(currentResetPos);
			return true;
		}
		
		/// <summary> Faster version of moveOtherIters typically used for a phrase 
		/// (more generally if ordered + max window constraint).
		/// Iterates faster on first iterator, which "drags" others
		/// </summary>
		private bool phraseNextBefore(int rangeEnd)
		{
			for (; ; )
			{
				if (fastest.NodeId >= rangeEnd || !fastest.nextBefore(rangeEnd))
					return false;
				int start = iterators[0].NodeId;
				for (int i = 1, size = iterators.Length; i < size; i++)
				{
					if (!iterators[i].skipToNode(start, rangeEnd))
						return false;
					start = iterators[i].NodeId;
					if (start >= rangeEnd)
						return false;
				}
				computeBounds();
				if (constraints != null && !checkFTConstraints(currentResetPos, rangeEnd) || !checkFilters())
					continue;
				return true;
			}
		}
		
		protected internal virtual void  computeBounds()
		{
			if (ordered)
			{
				curNodeId = iterators[0].NodeId;
				// BUG : max
				matchEnd = iterators[iterators.Length - 1].NodeEnd;
			}
			else
			{
				curNodeId = System.Qizx.Queries.Iterators.PostingIterator_Fields.MAX_NODEID;
				matchEnd = - 1;
				for (int p = 0, cnt = iterators.Length; p < cnt; p++)
				{
					int s = iterators[p].NodeId;
					int e = iterators[p].NodeEnd;
					if (s < curNodeId)
						curNodeId = s;
					if (e > matchEnd)
						matchEnd = e;
				}
			}
		}
		
		public override float computeWeighting(Scorer scorer)
		{
			float[] subWeights = new float[iterators.Length];
			for (int i = 0; i < iterators.Length; i++)
			{
				subWeights[i] = iterators[i].computeWeighting(scorer);
			}
			weightNorm = scorer.normAll(subWeights);
			return weight / weightNorm; // propagate to upper level
		}
		
		public override float computeScore(Scorer scorer)
		{
			if (subScores == null)
				subScores = new float[iterators.Length];
			for (int i = 0; i < iterators.Length; i++)
			{
				subScores[i] = iterators[i].computeScore(scorer);
			}
			return weight * weightNorm * scorer.scoreAll(subScores);
		}
	}
}